/*-
 * Copyright (c) 2010 mod_cfp team <mod-cfp@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: b00000.c 156 2010-07-07 07:26:12Z mod.cfp $
 */

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mod_cfp.h"

typedef int exm_callback_t(struct cfp_sess *);

struct exm_urls {
	const char	*url;
	exm_callback_t	*callback;
};

enum exm_step {
	EXM_STEP_FIRST,
	EXM_STEP_RESOLVE,
	EXM_STEP_CONNECT,
	EXM_STEP_GOOD,
	EXM_STEP_DONE
};

/* lightweight addrinfo */
struct exm_addr {
	int		ea_family;
	int		ea_socktype;
	int		ea_protocol;
	socklen_t	ea_addrlen;
	union {
		struct sockaddr_storage	_storage;
		struct sockaddr		sa;
	} ea_addr;
};

struct exm_priv {
	enum exm_step	step;
	int		sd;
	struct exm_addr	**ea;
	int		neaddr;
	exm_callback_t	*callback;
};

static int	exm_abc(struct cfp_sess *);

struct exm_urls urls[] = {
	{ "/abc",	exm_abc }
};

static void
exm_attach(struct cfp_module *cm)
{

	cfp_log(cm, "HERE");
	(void)cm;
}

static void
exm_detach(struct cfp_module *cm)
{

	(void)cm;
}

static int
exm_lookup(struct cfp_sess *sp)
{
#define	N(a)	(sizeof(a) / sizeof((a)[0]))
	struct exm_urls *u;
	struct cfp_txt url;
	int i, len, ret;

	for (i = 0; i < N(urls); i++) {
		u = &urls[i];

		ret = cfp_get_requrl(sp, &url);
		if (ret == -1)
			return (CFP_MATCH_NONE);

		len = MAX(strlen(u->url), CFP_TXT_LEN(&url));
		if (!strncmp(u->url, url.b, len)) {
			sp->arg = u->callback;
			return (CFP_MATCH_OK);
		}
	}
	return (CFP_MATCH_NONE);
#undef N
}

static int
exm_init(struct cfp_sess *sp)
{
	struct exm_priv *ep;

	ep = calloc(1, sizeof(*ep));
	assert(ep != NULL);
	ep->step = EXM_STEP_FIRST;
	ep->callback = (exm_callback_t *)sp->arg;

	sp->arg = ep;

	return (CFP_RET_DONE);
}

static void
exm_fini(struct cfp_sess *sp)
{
	struct exm_priv *ep = (struct exm_priv *)sp->arg;

	assert(ep != NULL);
	free(ep);
}

static int
exm_start(struct cfp_sess *sp)
{
	struct exm_priv *ep = (struct exm_priv *)sp->arg;
	int ret;

	do {
		ret = ep->callback(sp);
	} while (ret == CFP_RET_CONTINUE);

	return (ret);
}

static void
exm_resolve(struct cfp_sess *sp)
{
	struct addrinfo hints, *res0, *res;
	struct exm_addr **ea;
	struct exm_priv *ep = (struct exm_priv *)sp->arg;
	const char *addr = "www.google.com";
	const char *port = "80";
	int i, ret;

	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;
	ret = getaddrinfo(addr, port, &hints, &res0);
	assert(ret == 0);
	assert(res0 != NULL);
	for (res = res0, i = 0; res != NULL; res = res->ai_next, ++i)
		; /* nothing */
	assert(i != 0);
	ea = calloc(i, sizeof(*ea));
	assert(ea != NULL);
	ep->ea = ea;
	for (res = res0, i = 0; res != NULL; res = res->ai_next, ++i) {
		ea[i] = calloc(1, sizeof(*ea[i]));
		assert(ea[i] != NULL);
                ea[i]->ea_family = res->ai_family;
                ea[i]->ea_socktype = res->ai_socktype;
                ea[i]->ea_protocol = res->ai_protocol;
                ea[i]->ea_addrlen = res->ai_addrlen;
                assert(ea[i]->ea_addrlen <= sizeof(ea[i]->ea_addr));
                memcpy(&ea[i]->ea_addr, res->ai_addr, ea[i]->ea_addrlen);
	}
	freeaddrinfo(res0);
	ep->neaddr = i;
}

static int
exm_connect(struct cfp_sess *sp)
{
	const struct exm_addr *ea;
	struct exm_priv *ep = (struct exm_priv *)sp->arg;
	int i, n, retval, sd;

	for (n = 0; n < ep->neaddr; n++) {
		ea = ep->ea[n];
		sd = socket(ea->ea_family, ea->ea_socktype, ea->ea_protocol);
		assert(sd >= 0);
		ep->sd = sd;

		/* set the socket as non-blocking */
		i = 1;
		assert(ioctl(sd, FIONBIO, &i) == 0);

		retval = connect(sd, &ea->ea_addr.sa, ea->ea_addrlen);
		if (retval == 0)
			return (0);
		if (retval != 0 && errno == EINPROGRESS)
			return (1);
		assert(0 == 1);
	}
	assert(0 == 1);
	return (-1);
}

static int
exm_abc(struct cfp_sess *sp)
{
	struct exm_priv *ep = (struct exm_priv *)sp->arg;
	int ret;

	switch (ep->step) {
	case EXM_STEP_FIRST:
		cfp_fprintf(sp, "Hello World!");
		ep->step = EXM_STEP_RESOLVE;
		return (CFP_RET_CONTINUE);
		/* NOTREACHED */
	case EXM_STEP_RESOLVE:
		exm_resolve(sp);
		ep->step = EXM_STEP_CONNECT;
		return (CFP_RET_CONTINUE);
		/* NOTREACHED */
	case EXM_STEP_CONNECT:
		ep->step = EXM_STEP_GOOD;
		ret = exm_connect(sp);
		if (ret == 1) {
			sp->fd = ep->sd;
			return (CFP_RET_SOCKET_WRITABLE);
		}
		return (CFP_RET_CONTINUE);
		/* NOTREACHED */
	case EXM_STEP_GOOD:
		cfp_fprintf(sp, "Done");
		break;
	default:
		break;
	}
	return (CFP_RET_DONE);
}

struct cfp_conf conf = {
	.magic	= CFP_CONF_MAGIC,
	.attach	= exm_attach,
	.detach = exm_detach,
	.lookup = exm_lookup,
	.init = exm_init,
	.fini = exm_fini,
	.start = exm_start,
};
